From 126c33520b3e426f3a12ee69d6e6f93e48edff36 Mon Sep 17 00:00:00 2001
From: Fuwn <contact@fuwn.me>
Date: Sun, 4 Feb 2024 20:45:14 -0800
Subject: feat(badges): import mode

---
 src/routes/user/[user]/badges/+page.svelte | 153 ++++++++++++++++++++++++++++-
 1 file changed, 150 insertions(+), 3 deletions(-)

(limited to 'src/routes/user/[user]/badges/+page.svelte')

diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte
index a5ae83fe..523f9030 100644
--- a/src/routes/user/[user]/badges/+page.svelte
+++ b/src/routes/user/[user]/badges/+page.svelte
@@ -15,11 +15,19 @@
 	import Message from '$lib/Loading/Message.svelte';
 	import Dropdown from '$lib/Dropdown.svelte';
 	import AnimeRateLimited from '$lib/Error/AnimeRateLimited.svelte';
+	import { activityText } from '$lib/AniList/activity.js';
+	import SettingHint from '$lib/Settings/SettingHint.svelte';
 	// import { io } from 'socket.io-client';
 
 	export let data;
 
+	interface ImportImage {
+		link?: string;
+		image: string;
+	}
+
 	let editMode = false;
+	let importMode = false;
 	let currentUserIdentity: ReturnType<typeof userIdentity>;
 	let error: null | string;
 	// const socket = io();
@@ -30,7 +38,11 @@
 	let confirmDelete = 0;
 	let selectedBadge: Badge | undefined = undefined;
 	let loadError: string | null = null;
+	const isId = /^\d+$/.test(data.username);
+	let importImages: ImportImage[] | undefined = undefined;
 	// let badgeCount = 0;
+	let importLinks = false;
+	let importCategory = '';
 
 	// $: downloadDisabled = badgeCount > 20;
 
@@ -50,7 +62,6 @@
 	onMount(async () => {
 		// socket.on('badges', (message) => (badges = message));
 
-		const isId = /^\d+$/.test(data.username);
 		const badger = isId
 			? {
 					id: parseInt(data.username),
@@ -67,7 +78,7 @@
 		badgesPromise = fetch(root(`/api/badges?id=${badger.id}`));
 		awcPromise = fetch(proxy(`https://awc.moe/challenger/${badger.name}`));
 
-		if (data.user) {
+		if (data.user && !isId) {
 			currentUserIdentity = userIdentity(data.user);
 
 			// socket.emit('badges', data.user);
@@ -311,6 +322,71 @@
 
 		return url;
 	};
+
+	const parsePost = async () => {
+		if (importImages && importImages.length > 0) importImages = undefined;
+
+		const link = (document.querySelector('#import_activity_url') as HTMLInputElement).value;
+		const type = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$1');
+		const id = link.replace(/.*\/(activity|thread)\/(\d+).*/, '$2');
+
+		if (type !== 'activity') return null;
+
+		let text = await activityText(parseInt(id));
+
+		const images: ImportImage[] = [];
+
+		if (importLinks) {
+			Array.from(new DOMParser().parseFromString(text, 'text/html').querySelectorAll('a')).forEach(
+				(a) => {
+					const anchor = a as HTMLAnchorElement;
+
+					if (anchor.querySelector('img')) {
+						images.push({
+							link: anchor.href,
+							image: (anchor.querySelector('img') as HTMLImageElement).src
+						});
+					}
+				}
+			);
+
+			text = text.replace(/<a.*?>.*?<img.*?>.*?<\/a>/g, '');
+
+			Array.from(
+				new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
+			).forEach((img) => {
+				const image = img as HTMLImageElement;
+
+				images.push({
+					image: image.src
+				});
+			});
+		} else {
+			Array.from(
+				new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')
+			).forEach((img) => {
+				const image = img as HTMLImageElement;
+
+				images.push({
+					image: image.src
+				});
+			});
+		}
+
+		importImages = images;
+	};
+
+	const importBadges = () =>
+		importImages?.forEach((image) => {
+			badgesPromise = fetch(
+				`/api/badges?image=${encodeURIComponent(image.image)}&post=${encodeURIComponent(
+					image.link || '#'
+				)}${importCategory.length > 0 ? `&category=${encodeURIComponent(importCategory)}` : ''}`,
+				{
+					method: 'PUT'
+				}
+			);
+		});
 </script>
 
 <HeadTitle route={`${data.username}'s Badge Wall`} path={`/user/${data.username}`} />
@@ -325,7 +401,7 @@
 
 		<Skeleton grid={true} count={100} width="150px" height="170px" />
 	{:then identity}
-		{@const isOwner = identity && identity.name === data.username}
+		{@const isOwner = identity && (isId ? identity.id : identity.name) === data.username}
 
 		{#await badgesPromise}
 			<Message message="Loading badges ..." />
@@ -399,6 +475,18 @@
 											? $locale().user.badges.editMode.disable
 											: $locale().user.badges.editMode.enable}
 									</button>
+									<span style="margin: 0 0.625rem;">•</span>
+									<button
+										on:click={() => {
+											if (importMode) selectedBadge = undefined;
+
+											importMode = !importMode;
+										}}
+									>
+										{importMode
+											? $locale().user.badges.importMode.disable
+											: $locale().user.badges.importMode.enable}
+									</button>
 
 									{#if editMode && isOwner}
 										{@const groups = groupedBadges
@@ -581,6 +669,65 @@
 	{/await}
 {/if}
 
+{#if importMode}
+	<div class="popup popup-fullscreen">
+		<div class="card">
+			<SettingHint>Import badges from an activity post</SettingHint>
+
+			<p />
+
+			<input
+				type="text"
+				placeholder={$locale().user.badges.editMode.activityURL}
+				id="import_activity_url"
+				minlength="1"
+				maxlength="1000"
+				size="20"
+			/>
+			<input
+				type="text"
+				placeholder={$locale().user.badges.editMode.category}
+				id="import_category"
+				minlength="1"
+				maxlength="1000"
+				size="20"
+			/>
+
+			<p />
+
+			<input type="checkbox" id="import_links" name="import_links" bind:checked={importLinks} />
+			Import Links
+			<SettingHint lineBreak>
+				If your images are nested in links, check this box to import the links as well.
+			</SettingHint>
+
+			<p />
+
+			<button
+				on:click={() => {
+					importMode = false;
+					importImages = undefined;
+				}}
+				class="button-lined"
+			>
+				{$locale().user.badges.importMode.cancel}
+			</button>
+			<button on:click={() => parsePost()} class="button-lined" style="float: right;">
+				{$locale().user.badges.importMode.fetch}
+			</button>
+
+			{#if importImages && importImages.length > 0}
+				<p />
+
+				Import {importImages.length} badges?&nbsp;
+				<button on:click={() => importBadges()} class="button-lined no-shadow">
+					{$locale().user.badges.importMode.import}
+				</button>
+			{/if}
+		</div>
+	</div>
+{/if}
+
 <style>
 	/* body {
 		margin: 0;
-- 
cgit v1.2.3

